﻿using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
using IdentityServer4.EntityFramework.Mappers;
using IdentityServer4.Models;
using K9Nano.Admin.Data;
using K9Nano.Dependency;
using K9Nano.Share.Exceptions;
using Microsoft.EntityFrameworkCore;
using Entities = IdentityServer4.EntityFramework.Entities;

namespace K9Nano.Admin.Identity
{
    public class ClientAppService : IClientAppService, IScopedDependency
    {
        protected readonly K9ConfigurationDbContext DbContext;
        protected readonly IMapper Mapper;

        public ClientAppService(K9ConfigurationDbContext dbContext, IMapper mapper)
        {
            DbContext = dbContext;
            Mapper = mapper;
        }

        protected virtual async ValueTask LoadAsync(IQueryable<Entities.Client> baseQuery, CancellationToken cancellation)
        {
            await baseQuery.Include(x => x.AllowedCorsOrigins).SelectMany(c => c.AllowedCorsOrigins)
                .LoadAsync(cancellationToken: cancellation);
            await baseQuery.Include(x => x.AllowedGrantTypes).SelectMany(c => c.AllowedGrantTypes)
                .LoadAsync(cancellationToken: cancellation);
            await baseQuery.Include(x => x.AllowedScopes).SelectMany(c => c.AllowedScopes)
                .LoadAsync(cancellationToken: cancellation);
            await baseQuery.Include(x => x.Claims).SelectMany(c => c.Claims).LoadAsync(cancellationToken: cancellation);
            await baseQuery.Include(x => x.ClientSecrets).SelectMany(c => c.ClientSecrets)
                .LoadAsync(cancellationToken: cancellation);
            await baseQuery.Include(x => x.IdentityProviderRestrictions).SelectMany(c => c.IdentityProviderRestrictions)
                .LoadAsync(cancellationToken: cancellation);
            await baseQuery.Include(x => x.PostLogoutRedirectUris).SelectMany(c => c.PostLogoutRedirectUris)
                .LoadAsync(cancellationToken: cancellation);
            await baseQuery.Include(x => x.Properties).SelectMany(c => c.Properties)
                .LoadAsync(cancellationToken: cancellation);
            await baseQuery.Include(x => x.RedirectUris).SelectMany(c => c.RedirectUris).LoadAsync(cancellationToken: cancellation);
        }

        public virtual async ValueTask<IReadOnlyList<Client>> GetAllAsync(CancellationToken cancellation)
        {
            var baseQuery = DbContext.Clients.AsQueryable();
            await LoadAsync(baseQuery, cancellation);
            var clients = await baseQuery.ToListAsync(cancellation);
            return clients.Select(x => x.ToModel()).ToList();
        }

        public virtual async Task<Client> FindClientByIdAsync(string clientId, CancellationToken cancellation)
        {
            var baseQuery = DbContext.Clients
                .Where(x => x.ClientId == clientId);

            var client = (await baseQuery.ToArrayAsync(cancellationToken: cancellation))
                .SingleOrDefault();
            if (client == null) return null;

            await LoadAsync(baseQuery, cancellation);
            var model = client.ToModel();

            return model;
        }

        public virtual async ValueTask CreateAsync(Client client, CancellationToken cancellation)
        {
            var exists = await DbContext.Clients
                .AnyAsync(x => x.ClientId == client.ClientId, cancellation);

            if (exists)
            {
                throw new K9UserFriendlyException("Client已经存在: " + client.ClientId);
            }
            var entity = client.ToEntity();
            await DbContext.Clients.AddAsync(entity, cancellation);
            await DbContext.SaveChangesAsync(cancellation);
        }

        public virtual async ValueTask DeleteAsync(string clientId, CancellationToken cancellation)
        {
            var entity = await DbContext.Clients.FirstOrDefaultAsync(x => x.ClientId == clientId, cancellation);
            if (entity != null)
            {
                DbContext.Clients.Remove(entity);
                await DbContext.SaveChangesAsync(cancellation);
            }
        }

        public virtual async ValueTask SetEnableAsync(string clientId, bool enable, CancellationToken cancellation)
        {
            var entity = await DbContext.Clients.FirstOrDefaultAsync(x => x.ClientId == clientId, cancellation);
            if (entity != null && entity.Enabled != enable)
            {
                entity.Enabled = enable;
                await DbContext.SaveChangesAsync(cancellation);
            }
        }
    }
}